package com.luoyx.vjsb.common.aspect;

import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.luoyx.vjsb.common.annotation.Logs;
import com.luoyx.vjsb.common.event.SysEvent;
import com.luoyx.vjsb.common.util.AuthUtil;
import com.luoyx.vjsb.common.util.IpInfoUtil;
import com.luoyx.vjsb.common.vo.SysLogVO;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;

/**
 * <p>
 * 日志AOP
 * </p>
 *
 * @author luoyuanxiang <p>luoyuanxiang.github.io</p>
 * @since 2020/4/7 14:00
 */
@Slf4j
@Aspect
@Component
public class SysLogAspect {

    /**
     * 事件发布是由ApplicationContext对象管控的，我们发布事件前需要注入ApplicationContext对象调用publishEvent方法完成事件发布
     */
    @Resource
    private ApplicationContext applicationContext;

    @Resource
    private AuthUtil authUtil;

    @Resource
    private IpInfoUtil ipInfoUtil;

    /**
     * 切入点
     */
    @Pointcut("@annotation(com.luoyx.vjsb.common.annotation.Logs)")
    public void point() {}

    /**
     * 环绕通知
     *
     * @param pjp 1
     * @return 1
     */
    @Around("point()")
    public Object around(ProceedingJoinPoint pjp) {
        long startTime = System.currentTimeMillis();
        Object result = null;
        try {
            result = pjp.proceed();
            saveSysLog(pjp, null, startTime);
        } catch (Throwable throwable) {
            saveSysLog(pjp, throwable, startTime);
        }
        return result;
    }

    private void saveSysLog(ProceedingJoinPoint pjp, Throwable throwable, long startTime) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert attributes != null;
        HttpServletRequest request = attributes.getRequest();
        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        // 获取切入点所在的方法
        Method method = signature.getMethod();
        Logs logs = method.getAnnotation(Logs.class);
        SysLogVO sysLog = new SysLogVO();
        String value = logs.value();

        sysLog.setUa(request.getHeader("User-Agent"))
                .setIp(ipInfoUtil.getIpAddr(request))
                .setUserName(authUtil.getUsername())
                .setContent(value)
                .setLogLevel("INFO")
                .setParams(JSONUtil.toJsonPrettyStr(pjp.getArgs()))
                .setRequestMethod(request.getMethod())
                .setConsumingTime(System.currentTimeMillis() - startTime)
                .setCreateTime(DateUtil.date());
        if (throwable != null) {
            sysLog.setExMsg(throwable.getMessage())
                    .setExDetail(getStackTrace(throwable))
                    .setLogLevel("ERROR");
        }
        applicationContext.publishEvent(new SysEvent(sysLog));
    }

    /**
     * 获取堆栈信息
     *
     * @param throwable 异常信息
     * @return string
     */
    private String getStackTrace(Throwable throwable) {
        StringWriter sw = new StringWriter();
        try (PrintWriter pw = new PrintWriter(sw)) {
            throwable.printStackTrace(pw);
            return sw.toString();
        }
    }

}
